<?php
namespace WDB\WebUI;
use WDB,
    WDB\Exception;

/**
  * WebUI for a table wrapper.
  *
  * @author Richard Ejem <richard(at)ejem.cz>
  * @package WDB
  */
class Table extends WDB\BaseObject implements iTable
{
    /** @var iTable[] register all instances here for common calling handleRequestAll */
    protected static $instances = array();

    /** @var string[] specifies order of columns in output for OrderedIterator*/
    protected $columnOrder = array();

    /**@var string template set identifier*/
    protected $templateSet = 'default';

    /** @var \WDB\Wrapper\Table*/
    protected $tableWrapper;

    /** @var iRequest */
    protected $request;

    /** @var bool this is set to TRUE after calling handleRequest to prevent performing actions twice.*/
    protected $doneEvents = FALSE;

    /**@var string name of Template Adapter class*/
    protected $templateAdapterClass = '\\WDB\\WebUI\\SimpleTemplateAdapter';

    /**@var iTemplateAdapter*/
    protected $paginatorTemplate = NULL;

    /**@var WDB\Wrapper\Record */
    protected $selectedRecord = NULL;

    /**@var Column[] $columns*/
    protected $columns;

    /**
     * Raised with \WDB\Wrapper\iRecord as argument
     *
     * @var \WDB\Event\Event*/
    protected $onValidateInput;

    /**
     * @var int records shown per page
     * @todo allow customization
     */
    protected $recordsPerPage = 10;

    /**
     * Names of column WebUI classes for this table in form of {column name}=>{class name}
     *
     * @var string[]
     */
    protected $columnWebUIClasses;

    /**
     * @var boolean
     */
    protected $newRecordCapable;

    /**
     * @var array
     */
    protected $validationErrors;

    // <editor-fold defaultstate="collapsed" desc="global instance handling">
    /**
     * Register iTable instance to call handleRequest on all tables with ::handleRequestAll().
     *
     * Table class is automatically registered in constructor, you can also register
     * your own iTable implementations here.
     *
     * @param iTable
     */
    public static function registerInstance(iTable $t)
    {
        self::$instances[] = $t;
    }

    /**
     * call handleRequest on all registered instances of iTable.
     */
    public static function handleRequestAll()
    {
        foreach (self::$instances as $instance)
        {
            $instance->handleRequest();
        }
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="iTable implementation">

    public function getName()
    {
        return $this->tableWrapper->getName();
    }

    public function getTitle()
    {
        return $this->tableWrapper->getTitle();
    }

    public function getTableLocator()
    {
        if (method_exists($this->tableWrapper, 'getTableLocator'))
        {
            return $this->tableWrapper->getTableLocator();
        }
        else
        {
            return NULL;
        }
    }

    public function getSelectedRecord()
    {
        if (!$this->doneEvents) $this->handleRequest();
        return $this->selectedRecord;
    }

    public function getRequest()
    {
        if (!$this->request instanceof iRequest)
        {
            $this->request = new HttpRequest($this->tableWrapper->name);
        }
        return $this->request;
    }

    public function setRequest(iRequest $rq)
    {
        $this->request = $rq;
    }

    public function handleRequest()
    {
        if ($this->doneEvents) return;
        $this->doneEvents = TRUE;
        if ($this->getRequest()->getSelectedRecordKey() !== NULL)
        {
            $this->selectedRecord = $this->tableWrapper->getRecordByStringPK($this->getRequest()->getSelectedRecordKey());
        }
        elseif ($this->getRequest()->isNewRecord())
        {
            $this->selectedRecord = $this->tableWrapper->newRecord();
        }
        if ($this->selectedRecord && $this->getRequest()->isEditSubmitted())
        {
            $this->parseInput($this->selectedRecord);
            try
            {
                if ($this->selectedRecord->save())
                {
                    $this->getRequest()->redirectSuccessfulSave();
                }
            }
            catch (Exception\ValidationFailed $ex)
            {
            }
        }
        if ($this->getRequest()->isDeleteRequest())
        {
            try
            {
                $rec = $this->tableWrapper->getRecordByStringPK($this->getRequest()->getDeleteKey());
                $rec->delete();
            }
            catch (Exception\NotFound $ex)
            {}
            $this->getRequest()->redirectSuccessfulSave();
        }
    }

    protected function prepareTemplate($state = iTable::STATE_AUTO)
    {
        if ($state == iTable::STATE_AUTO)
        {
            $state = $this->getRequest()->getRequestedState();
        }
        $tgen = new $this->templateAdapterClass($this->templateSet.'.'.$state);
        $tgen->table = $this;
        if ($state == iTable::STATE_LIST)
        {
            $tgen->records = $this->tableWrapper->elevateRecords($this->getDatasource()->fetch());
        }
        return $tgen;
    }

    public function setTemplateAdapterClass(iTemplateAdapter $manager)
    {
        $this->templateAdapterClass = $manager;
    }

    /**
     *
     * @return WDB\WebUI\iTemplateAdapter
     */
    public function getPaginatorTemplate()
    {
        //initialize paginator (performed once)
        if ($this->paginatorTemplate === NULL)
        {
            $this->paginatorTemplate = new $this->templateAdapterClass($this->templateSet.'.paginator');
            $this->paginatorTemplate->around = 3; //TODO allow customization
            $this->paginatorTemplate->uri = $this->getRequest()->getStateLink(\WDB\WebUI\iTable::STATE_LIST);
            $this->paginatorTemplate->uriPageParam = $this->getRequest()->pageParamName;
        }

        //configure paginator (apply current Table state)
        if ($this->recordsPerPage > 0)
        {
            $pages = ceil(max(1, $this->count()) / $this->recordsPerPage);
        }
        else
        {
            $pages = 1;
        }

        $cpage = max(1, min($pages, $this->getRequest()->getCurrentPage()+1));
        $this->paginatorTemplate->currentPage = $cpage;
        $this->paginatorTemplate->pages = $pages;

        return $this->paginatorTemplate;
    }

    public function show($state = iTable::STATE_AUTO)
    {
        if (!$this->doneEvents) $this->handleRequest();
        $this->prepareTemplate($state)->show();
    }
    public function generateHTML($state = iTable::STATE_AUTO)
    {
        if (!$this->doneEvents) $this->handleRequest();
        return $this->prepareTemplate($state)->parse();
    }

    public function getColumns()
    {
        return new WDB\Utils\OrderedIterator($this->columns, $this->columnOrder);
    }

    public function createUniqueColumnName()
    {
        $maxid = 0;
        foreach ($this->columns as $column)
        {
            if (preg_match('~unnamed([0-9]+)~', $column->name, $m))
            {
                if ($m[1] > $maxid) $maxid = $m[1];
            }
        }
        return 'unnamed'.($maxid+1);
    }

    public function addColumn(iColumn $column, $after = iTable::LAST)
    {
        $this->columns[$column->name] = $column;
        if ($after === iTable::LAST)
        {
            $this->columnOrder[] = $column->name;
        }
        elseif ($after === iTable::FIRST)
        {
            array_unshift($this->columnOrder, $column->name);
        }
        else
        {
            $offset = array_search($after, $this->columnOrder, TRUE);
            if ($offset === FALSE)
            {
                //not found, add as last
                $this->columnOrder[] = $column->name;
            }
            else
            {
                //found
                array_splice($this->columnOrder, $offset+1, 0, $column->name);
            }
        }
    }

    public function order()
    {
        $notOrdered = array_diff($this->columnOrder, func_get_args());
        $this->columnOrder = array();
        foreach (func_get_args() as $arg)
        {
            $cname = $this->tableWrapper->getColumnCName($arg);
            if (isset($this->columns[$cname]))
            {
                $this->columnOrder[] = $cname;
            }
        }
        $this->columnOrder = array_merge($this->columnOrder, $notOrdered);
        return $this;
    }

    public function getOnValidateInput()
    {
        return $this->onValidateInput;
    }

    public function parseInput(WDB\Wrapper\iRecord $record)
    {
        foreach ($this->columns as $column)
        {
            if ($column->isDisplayedEdit())
            {
                $column->parseInputValue($record, $this->getRequest()->getEdited($column->getName()));
            }
        }
        $this->onValidateInput->raise($record);
    }

    public function getDatasource()
    {
        $ds = $this->getRequest()->configureDatasource($this->tableWrapper);
        $ds->pageSize(10); //TODO customize
        return $ds;
    }

    public function isListedInSchemaAdmin()
    {
        return $this->tableWrapper->isListedInSchemaAdmin();
    }

    public function isWebUIAccessible()
    {
        return $this->tableWrapper->isWebUIAccessible();
    }

    public function getValidatorData(WDB\Wrapper\iRecord $record)
    {
        $data = array(
            'columns'=>$this->getValidatorColumns(),
            'tableLocator'=>$this->getTableLocator()->serialize(),
            'key'=>$record->getStringKey(),
        );
        foreach ($record->getValidators() as $validator)
        {
            if ($validator instanceof WDB\Validation\iValidator)
            {
                $validator->fetchValidatorData($record, $data);
            }
        }
        return $data;
    }

    public function getHTMLEditStatus()
    {
        if ($this->selectedRecord !== NULL)
        {
            return '<div class="wdb_tableValidationErrors">'.implode('<br>', $this->selectedRecord->getGlobalValidationErrors()).'</div>';
        }
        else
        {
            return '';
        }
    }

    // </editor-fold>

    public function __construct(WDB\Wrapper\Table $table)
    {
        self::registerInstance($this);
        $this->tableWrapper = $table;
        $this->newRecordCapable = true;
        foreach ($table->columns as $column)
        {
            $this->columns[$column->name] = ColumnFactory::create($column, $this);
            $this->columnOrder[] = $column->name;
        }
        $this->onValidateInput = new WDB\Event\Event($this);
    }

    protected function getValidatorColumns()
    {
        $columns = array();
        foreach ($this->columns as $column)
        {
            if (!$column->isDisplayedEdit()) continue;
            $columns[$column->name] = array(
                'nullable'=>$column->isNullable(),
                'title'=>$column->title
            );
            $columns[$column->name]['elementName']=$this->getRequest()->getSaveFieldName($column->getName()).'[value]';
            if ($column->isNullable())
            {
                $columns[$column->name]['nullCheckerName']=$this->getRequest()->getSaveFieldName($column->getName()).'[notnull]';
            }
        }
        return $columns;
    }

    public function getColumnWebUIClass($columnName)
    {
        if (isset($this->columnWebUIClasses[$columnName])) return $this->columnWebUIClasses[$columnName];
        $columns = $this->tableWrapper->getColumns();
        if (isset($columns[$columnName]))
        {
            return $columns[$columnName]->getWebUIClass();
        }
        return NULL;
    }

    public function isNewRecordCapable()
    {
        foreach ($this->columns as $column)
        {
            if (!$column->isDefaultInitializable() && !$column->isDisplayedEdit())
            {
                return false;
            }
        }
        return true;
    }

    // <editor-fold defaultstate="collapsed" desc="ArrayAcces, Countable and Iterator delegation to wrapper">
    public function count() { return $this->tableWrapper->count(); }
    public function offsetExists($offset) { return $this->tableWrapper->offsetExists($offset);}
    public function offsetGet($offset) { return $this->tableWrapper->offsetGet($offset);}
    public function offsetSet($offset, $value) { return $this->tableWrapper->offsetSet($offset, $value);}
    public function offsetUnset($offset) { return $this->tableWrapper->offsetUnset($offset);}
    public function current() { return $this->tableWrapper->current();}
    public function key() { return $this->tableWrapper->key();}
    public function next() { return $this->tableWrapper->next();}
    public function rewind() { return $this->tableWrapper->rewind();}
    public function valid() { return $this->tableWrapper->valid();}
    // </editor-fold>
}